package com.onpositive.knowledge.model.values;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;

import com.onpositive.knowledge.model.value.audits.Range;
import com.onpositive.semantic.model.api.property.ValueUtils;
import com.onpositive.semantic.model.api.property.java.annotations.TextLabel;

public class DescribedValue implements IHasValue, Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = -4904269867262387335L;
	
	private static final DescribedValue[] DESCRIBED_VALUES = new DescribedValue[0];
	public static final byte ABSOLUTE_TRUTH = Byte.MAX_VALUE;
	public static final byte INFERRED_TRUTH = Byte.MAX_VALUE / 2;
	public static final byte GUESS = Byte.MAX_VALUE / 5;

	public DescribedValue(Object value, Object comment) {
		super();
		this.inferred = Byte.MAX_VALUE;
		this.value = value;
		this.source = null;
		this.timeStamp = System.currentTimeMillis();
		this.commentString = comment;
	}
	
	private DescribedValue( DescribedValue other, Object value, String comment ) {
		super();
		this.inferred = other.inferred ;
		this.value = value;
		this.source = other.source;
		this.timeStamp = other.timeStamp ;
		this.commentString = other.commentString ;
	}

	public static DescribedValue[] toDescribedValues(Object value,
			String comment) {
		if (value != null) {
			if (value instanceof DescribedValue[]) {
				return (DescribedValue[]) value;
			}
			Iterable<Object> collection = ValueUtils.toCollection(value);
			ArrayList<DescribedValue> vl = new ArrayList<DescribedValue>();
			for (Object o : collection) {
				if (!(o instanceof DescribedValue)) {
					vl.add(new DescribedValue(o, comment));
				} else {
					vl.add((DescribedValue) o);
				}
			}
			return vl.toArray(new DescribedValue[vl.size()]);
		}
		return DESCRIBED_VALUES;
	}

	public DescribedValue(boolean inferred, Object value, Object source,
			Object comment) {
		super();
		this.inferred = inferred ? INFERRED_TRUTH : ABSOLUTE_TRUTH;
		this.value = value;
		this.source = source;
		this.timeStamp = System.currentTimeMillis();
		this.commentString = comment;
	}

	public DescribedValue(byte inferred, Object value, Object source,
			Object comment) {
		super();
		this.inferred = inferred;
		this.value = value;
		this.source = source;
		this.timeStamp = System.currentTimeMillis();
		this.commentString = comment;
	}

	public DescribedValue(byte inferred, Object value, Object source,
			long timeStamp, Object comment) {
		super();
		this.inferred = inferred;
		this.value = value;
		this.source = source;
		this.timeStamp = timeStamp;
		this.commentString = comment;
	}

	@TextLabel(value = "this>=127?'truth':this>=64?'inferred':'guessing'")
	
	protected final byte inferred;

	protected final Object value;

	protected final long timeStamp;

	protected final Object source;

	private final Object commentString;

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		//result = prime * result + ((comment == null) ? 0 : comment.hashCode());
//		result = prime * result + inferred;
//		result = prime * result + ((source == null) ? 0 : source.hashCode());
		//result = prime * result + (int) (timeStamp ^ (timeStamp >>> 32));
		result = prime * result + ((value == null) ? 0 : value.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		DescribedValue other = (DescribedValue) obj;
//		if (comment == null) {
//			if (other.comment != null)
//				return false;
//		} else if (!comment.equals(other.comment))
//			return false;
//		if (inferred != other.inferred)
//			return false;
//		if (source == null) {
//			if (other.source != null)
//				return false;
//		} else if (!source.equals(other.source))
//			return false;
//		if (timeStamp != other.timeStamp)
//			return false;
		if (value == null) {
			if (other.value != null)
				return false;
		} else if (!value.equals(other.value))
			return false;
		return true;
	}

	public boolean isBetter(DescribedValue v1) {
		return chooseBest(Arrays.asList(new DescribedValue[] { this, v1 })) == this;
	}

	public static DescribedValue chooseBest(Iterable<DescribedValue> values) {
		byte m = 0;
		if (values == null) {
			return null;
		}
		DescribedValue maxValue = null;
		for (DescribedValue v : values) {
			if (v == null) {
				continue;
			}
			if (maxValue == null) {
				maxValue = v;
				m = maxValue.inferred;
				continue;
			}
			if (m == v.inferred) {
				if (hasBetterValue(maxValue.value, v.value)) {
					maxValue = v;
					continue;
				}
				if (v.timeStamp > maxValue.timeStamp) {
					maxValue = v;
				}
				continue;
			}
			if (v.inferred > m) {
				m = v.inferred;
				maxValue = v;
			}
		}
		return maxValue;
	}

	public static boolean hasBetterValue(Object v1, Object v2) {
		if (v1 instanceof Range) {
			if (!(v2 instanceof Range)) {
				return true;
			}
			Range r1 = (Range) v1;
			Range r2 = (Range) v2;
			if (r2.includes(r1)) {
				return true;
			}
		}
		return false;
	}

	public String toString() {
		if (value != null) {
			return value.toString();
		}
		return "";
	}

	public byte getInferred() {
		return inferred;
	}

	public Object value() {
		return value;
	}

	public long getTimestamp() {
		return timeStamp;
	}

	public Object getSource() {
		return source;
	}

	public Object getComment() {
		if( value instanceof IHasValue )
		{
			StringBuilder bld = new StringBuilder( this.commentString.toString() ) ;
			Object comment = ((IHasValue)value).getComment();
			if( comment != null ){
				bld.append("(") ;				
				bld.append( comment.toString() ) ;
				bld.append(")") ;
			}
			
			final String fullcomment = bld.toString();
			return fullcomment ;
		}
		return commentString;
	}

	@Override
	public IHasValue clone(Object value, String comment) {		
		return new DescribedValue(this, value, comment) ;
	}
}
